package com.aphrodite.io.sip.call;

import com.aphrodite.io.sip.address.NameAddress;
import com.aphrodite.io.sip.dialog.InviteDialog;
import com.aphrodite.io.sip.dialog.InviteDialogListener;
import com.aphrodite.io.sip.header.MultipleHeader;
import com.aphrodite.io.sip.message.Message;
import com.aphrodite.io.sip.provider.SipProvider;
import com.aphrodite.util.log.GlobalLogger;

/**
 * Class Call implements SIP calls.
 * <p>
 * It handles both outgoing or incoming calls.
 * <p>
 * Both offer/answer models are supported, that is: <br>
 * i) offer/answer in invite/2xx, or <br>
 * ii) offer/answer in 2xx/ack
 */
public class Call implements InviteDialogListener {
	protected static GlobalLogger log;

	/** The user contact url */
	protected String contact_url;

	/** The invite dialog (sip.dialog.InviteDialog) */
	protected InviteDialog dialog;

	/** The user url (AOR) */
	protected String from_url;

	/** The call listener (sipx.call.CallListener) */
	CallListener listener;

	/** The local sdp */
	protected String local_sdp;

	/** The remote sdp */
	protected String remote_sdp;
	/** Event logger. */
	// Log log;
	/** The SipProvider used for the call */
	protected SipProvider sip_provider;

	/**
	 * Creates a new Call for the already received INVITE request <i>invite</i>.
	 */
	public Call(SipProvider sip_provider, Message invite, String contact_url, CallListener call_listener) {
		this.sip_provider = sip_provider;
		this.log = sip_provider.getLog();
		this.listener = call_listener;
		this.from_url = invite.getToHeader().getNameAddress().toString();
		this.contact_url = contact_url;
		this.dialog = new InviteDialog(sip_provider, invite, this);
		this.local_sdp = null;
		this.remote_sdp = invite.getBody();
	}

	/** Creates a new Call specifing the sdp */
	/*
	 * public Call(SipProvider sip_provider, String from_url, String
	 * contact_url, String sdp, CallListener call_listener) {
	 * this.sip_provider=sip_provider; this.log=sip_provider.getLog();
	 * this.listener=call_listener; this.from_url=from_url;
	 * this.contact_url=contact_url; local_sdp=sdp; }
	 */

	/** Creates a new Call. */
	public Call(SipProvider sip_provider, String from_url, String contact_url, CallListener call_listener) {
		this.sip_provider = sip_provider;
		this.log = sip_provider.getLog();
		this.listener = call_listener;
		this.from_url = from_url;
		this.contact_url = contact_url;
		this.dialog = null;
		this.local_sdp = null;
		this.remote_sdp = null;
	}

	/** Gets the current invite dialog */
	/*
	 * public InviteDialog getInviteDialog() { return dialog; }
	 */

	/** Accepts the incoming call */
	public void accept(String sdp) {
		local_sdp = sdp;
		if (dialog != null)
			dialog.accept(contact_url, local_sdp);
	}

	/** Answers at the 2xx/offer (in the ack message) */
	public void ackWithAnswer(String sdp) {
		local_sdp = sdp;
		dialog.ackWithAnswer(contact_url, sdp);
	}

	/** Close the ongoing call */
	public void bye() {
		if (dialog != null)
			dialog.bye();
	}

	/** Starts a new call with the <i>invite</i> message request */
	public void call(Message invite) {
		dialog = new InviteDialog(sip_provider, this);
		local_sdp = invite.getBody();
		if (local_sdp != null)
			dialog.invite(invite);
		else
			dialog.inviteWithoutOffer(invite);
	}

	/** Starts a new call, inviting a remote user (<i>callee</i>) */
	public void call(String callee) {
		call(callee, null, null, null);
	}

	/** Starts a new call, inviting a remote user (<i>callee</i>) */
	public void call(String callee, String sdp) {
		call(callee, null, null, sdp);
	}

	/** Starts a new call, inviting a remote user (<i>callee</i>) */
	public void call(String callee, String from, String contact, String sdp) {
		log.info(this, "calling " + callee);
		if (from == null)
			from = from_url;
		if (contact == null)
			contact = contact_url;
		if (sdp != null)
			local_sdp = sdp;
		dialog = new InviteDialog(sip_provider, this);
		if (local_sdp != null)
			dialog.invite(callee, from, contact, local_sdp);
		else
			dialog.inviteWithoutOffer(callee, from, contact);
	}

	/** Cancels the outgoing call */
	public void cancel() {
		if (dialog != null)
			dialog.cancel();
	}

	/** Gets the current call-id */
	public String getCallId() {
		if (dialog == null)
			return null;
		else
			return dialog.getCallID();
	}

	/** Gets the current local session descriptor */
	public String getLocalSessionDescriptor() {
		return local_sdp;
	}

	/** Gets the current remote session descriptor */
	public String getRemoteSessionDescriptor() {
		return remote_sdp;
	}

	/**
	 * Closes an ongoing or incoming/outgoing call
	 * <p>
	 * It trys to fires refuse(), cancel(), and bye() methods
	 */
	public void hangup() {
		if (dialog != null) { // try dialog.refuse(), cancel(), and bye()
			// methods..
			dialog.refuse();
			dialog.cancel();
			dialog.bye();
		}
	}

	/** Whether the call is on (active). */
	public boolean isOnCall() {
		return dialog.isSessionActive();
	}

	/** Accepts the incoming call */
	/*
	 * public void accept() { accept(local_sdp); }
	 */

	/** Waits for an incoming call */
	public void listen() {
		dialog = new InviteDialog(sip_provider, this);
		dialog.listen();
	}

	/** Modify the current call */
	public void modify(String contact, String sdp) {
		local_sdp = sdp;
		if (dialog != null)
			dialog.reInvite(contact, local_sdp);
	}

	/**
	 * Inherited from class InviteDialogListener and called by an InviteDialag.
	 * Normally you should not use it. Use specific callback methods instead
	 * (e.g. onCallConfirmed()).
	 */
	public void onDlgAck(InviteDialog d, String sdp, Message msg) {
		if (d != dialog) {
			log.info(this, "NOT the current dialog");
			return;
		}
		if (sdp != null && sdp.length() != 0)
			remote_sdp = sdp;
		if (listener != null)
			listener.onCallConfirmed(this, sdp, msg);
	}

	/**
	 * Inherited from class InviteDialogListener and called by an InviteDialag.
	 * Normally you should not use it. Use specific callback methods instead
	 * (e.g. onClosing()).
	 */
	public void onDlgBye(InviteDialog d, Message msg) {
		if (d != dialog) {
			log.info(this, "NOT the current dialog");
			return;
		}
		if (listener != null)
			listener.onCallClosing(this, msg);
	}

	/**
	 * Inherited from class InviteDialogListener and called by an InviteDialag.
	 * Normally you should not use it. Use specific callback methods instead
	 * (e.g. onClosed()).
	 */
	public void onDlgByeFailureResponse(InviteDialog d, int code, String reason, Message msg) {
		if (d != dialog) {
			log.info(this, "NOT the current dialog");
			return;
		}
		if (listener != null)
			listener.onCallClosed(this, msg);
	}

	/**
	 * Inherited from class InviteDialogListener and called by an InviteDialag.
	 * Normally you should not use it. Use specific callback methods instead
	 * (e.g. onClosed()).
	 */
	public void onDlgByeSuccessResponse(InviteDialog d, int code, String reason, Message msg) {
		if (d != dialog) {
			log.info(this, "NOT the current dialog");
			return;
		}
		if (listener != null)
			listener.onCallClosed(this, msg);
	}

	/** When an incoming INVITE is accepted */
	// public void onDlgAccepted(InviteDialog dialog) {}
	/** When an incoming INVITE is refused */
	// public void onDlgRefused(InviteDialog dialog) {}
	/** When the INVITE handshake is successful terminated */
	public void onDlgCall(InviteDialog dialog) {}

	// ************** Inherited from InviteDialogListener **************

	/**
	 * Inherited from class InviteDialogListener and called by an InviteDialag.
	 * Normally you should not use it. Use specific callback methods instead
	 * (e.g. onCallClosing()).
	 */
	public void onDlgCancel(InviteDialog d, Message msg) {
		if (d != dialog) {
			log.info(this, "NOT the current dialog");
			return;
		}
		if (listener != null)
			listener.onCallCanceling(this, msg);
	}

	/** When an incoming Re-INVITE is accepted */
	// public void onDlgReInviteAccepted(InviteDialog dialog) {}
	/** When an incoming Re-INVITE is refused */
	// public void onDlgReInviteRefused(InviteDialog dialog) {}
	/** When a BYE request traqnsaction has been started */
	// public void onDlgByeing(InviteDialog dialog) {}
	/** When the dialog is finally closed */
	public void onDlgClose(InviteDialog dialog) {}

	/**
	 * Inherited from class InviteDialogListener and called by an InviteDialag.
	 * Normally you should not use it. Use specific callback methods instead
	 * (e.g. onCallIncoming()).
	 */
	public void onDlgInvite(InviteDialog d, NameAddress callee, NameAddress caller, String sdp, Message msg) {
		if (d != dialog) {
			log.info(this, "NOT the current dialog");
			return;
		}
		if (sdp != null && sdp.length() != 0)
			remote_sdp = sdp;
		if (listener != null)
			listener.onCallIncoming(this, callee, caller, sdp, msg);
	}

	/**
	 * Inherited from class InviteDialogListener and called by an InviteDialag.
	 * Normally you should not use it. Use specific callback methods instead
	 * (e.g. onCallRefused()).
	 */
	public void onDlgInviteFailureResponse(InviteDialog d, int code, String reason, Message msg) {
		if (d != dialog) {
			log.info(this, "NOT the current dialog");
			return;
		}
		if (listener != null)
			listener.onCallRefused(this, reason, msg);
	}

	/**
	 * Inherited from class InviteDialogListener and called by an InviteDialag.
	 * Normally you should not use it. Use specific callback methods instead
	 * (e.g. onCallRinging()).
	 */
	public void onDlgInviteProvisionalResponse(InviteDialog d, int code, String reason, String sdp, Message msg) {
		if (d != dialog) {
			log.info(this, "NOT the current dialog");
			return;
		}
		if (sdp != null && sdp.length() != 0)
			remote_sdp = sdp;
		if (code == 180)
			if (listener != null)
				listener.onCallRinging(this, msg);
	}

	/**
	 * Inherited from class InviteDialogListener and called by an InviteDialag.
	 * Normally you should not use it. Use specific callback methods instead
	 * (e.g. onCallRedirection()).
	 */
	public void onDlgInviteRedirectResponse(InviteDialog d, int code, String reason, MultipleHeader contacts,
			Message msg) {
		if (d != dialog) {
			log.info(this, "NOT the current dialog");
			return;
		}
		if (listener != null)
			listener.onCallRedirection(this, reason, contacts.getValues(), msg);
	}

	/**
	 * Inherited from class InviteDialogListener and called by an InviteDialag.
	 * Normally you should not use it. Use specific callback methods instead
	 * (e.g. onCallAccepted()).
	 */
	public void onDlgInviteSuccessResponse(InviteDialog d, int code, String reason, String sdp, Message msg) {
		if (d != dialog) {
			log.info(this, "NOT the current dialog");
			return;
		}
		if (sdp != null && sdp.length() != 0)
			remote_sdp = sdp;
		if (listener != null)
			listener.onCallAccepted(this, sdp, msg);
	}

	/**
	 * Inherited from class InviteDialogListener and called by an InviteDialag.
	 * Normally you should not use it. Use specific callback methods instead
	 * (e.g. onCallModifying()).
	 */
	public void onDlgReInvite(InviteDialog d, String sdp, Message msg) {
		if (d != dialog) {
			log.info(this, "NOT the current dialog");
			return;
		}
		if (sdp != null && sdp.length() != 0)
			remote_sdp = sdp;
		if (listener != null)
			listener.onCallModifying(this, sdp, msg);
	}

	/**
	 * Inherited from class InviteDialogListener and called by an InviteDialag.
	 * Normally you should not use it. Use specific callback methods instead
	 * (e.g. onCallReInviteRedirection()).
	 */
	// public void onDlgReInviteRedirectResponse(InviteDialog d, int code,
	// String reason, MultipleHeader contacts, Message msg)
	// { if (d!=dialog) { printLog("NOT the current dialog",LogLevel.HIGH);
	// return; }
	// if (listener!=null)
	// listener.onCallReInviteRedirection(this,reason,contacts.getValues(),msg);
	// }
	/**
	 * Inherited from class InviteDialogListener and called by an InviteDialag.
	 * Normally you should not use it. Use specific callback methods instead
	 * (e.g. onCallReInviteRefused()).
	 */
	public void onDlgReInviteFailureResponse(InviteDialog d, int code, String reason, Message msg) {
		if (d != dialog) {
			log.info(this, "NOT the current dialog");
			return;
		}
		if (listener != null)
			listener.onCallReInviteRefused(this, reason, msg);
	}

	/**
	 * Inherited from class InviteDialogListener and called by an InviteDialag.
	 * Normally you should not use it.
	 */
	public void onDlgReInviteProvisionalResponse(InviteDialog d, int code, String reason, String sdp, Message msg) {
		if (d != dialog) {
			log.info(this, "NOT the current dialog");
			return;
		}
		if (sdp != null && sdp.length() != 0)
			remote_sdp = sdp;
	}

	/**
	 * Inherited from class InviteDialogListener and called by an InviteDialag.
	 * Normally you should not use it. Use specific callback methods instead
	 * (e.g. onCallReInviteAccepted()).
	 */
	public void onDlgReInviteSuccessResponse(InviteDialog d, int code, String reason, String sdp, Message msg) {
		if (d != dialog) {
			log.info(this, "NOT the current dialog");
			return;
		}
		if (sdp != null && sdp.length() != 0)
			remote_sdp = sdp;
		if (listener != null)
			listener.onCallReInviteAccepted(this, sdp, msg);
	}

	/**
	 * Inherited from class InviteDialogListener and called by an InviteDialag.
	 * Normally you should not use it. Use specific callback methods instead
	 * (e.g. onCallReInviteTimeout()).
	 */
	public void onDlgReInviteTimeout(InviteDialog d) {
		if (d != dialog) {
			log.info(this, "NOT the current dialog");
			return;
		}
		if (listener != null)
			listener.onCallReInviteTimeout(this);
	}

	/**
	 * Inherited from class InviteDialogListener and called by an InviteDialag.
	 * Normally you should not use it. Use specific callback methods instead
	 * (e.g. onCallTimeout()).
	 */
	public void onDlgTimeout(InviteDialog d) {
		if (d != dialog) {
			log.info(this, "NOT the current dialog");
			return;
		}
		if (listener != null)
			listener.onCallTimeout(this);
	}

	/** Redirects the incoming call */
	public void redirect(String redirect_url) {
		if (dialog != null)
			dialog.redirect(302, "Moved Temporarily", redirect_url);
	}

	/** Refuses the incoming call */
	public void refuse() {
		if (dialog != null)
			dialog.refuse();
	}

	/** Respond to a incoming call (invite) with <i>resp</i> */
	public void respond(Message resp) {
		if (dialog != null)
			dialog.respond(resp);
	}

	// -----------------------------------------------------

	/** Rings back for the incoming call */
	public void ring() {
		if (dialog != null)
			dialog.ring();
	}

	/** Sets a new local session descriptor */
	public void setLocalSessionDescriptor(String sdp) {
		local_sdp = sdp;
	}

}
